package org.xjtu.xingole.javanaut.concurrency.future;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

/**
 * <p>
 * The Future class represents a future result of an asynchronous computation. This
 * result will eventually appear in the Future after the processing is complete.
 * 
 * <p>
 * Long running methods are good candidates for asynchronous processing and the Future
 * interface because we can execute other processes while we're waiting for the task
 * encapsulated in the Future to complete.
 * 
 * <p>
 * Some examples of operations that would leverage the async nature of Future are:
 * 
 * <ul>
 *  <li>computational intensive processes (mathematical and scientific calculations)</li>
 *  <li>manipulating large data structures (big data)</li>
 *  <li>remote method calls (downloading files, HTML scrapping, web services)</li>
 */
public class UsingFuture {

    public static void consumingFuture() throws Exception {
        var future = new SquareCalculator().calculate(10);

        // Future.isDone() tells us if the executor has finished processing the task.
        while (!future.isDone()) {
            System.out.println("Calculating...");
            Thread.sleep(300);
        }

        // Future.get() returns the actual result from the calculation. This method
        // will block the execution until the task is complete. In our case, this
        // won't be an issue because we'll check if the task is complete by calling
        // isDone().
        Integer result = future.get();

        System.out.println("Result is: " + result);
    }

    /**
     * <p>
     * ForkJoinTask is an abstract class which implements the Future, and is capable
     * of running a large number of tasks hosted by a small number of acutal threads
     * in ForkJoinPool.
     * 
     * <p>
     * The main characteristic of a ForkJoinTask is that it will usually spawn new
     * subtasks as part of the work required to complete its main task. It generates
     * a new tasks by calling fork(), and it gathers all results wiht join().
     * 
     * <p>
     * There are two abstract classes that implement ForkJoinTask: RecursiveTask, which
     * returns a value upon completion; and RecursiveAction, which doesn't return anything.
     */
    public static void consumingForkJoinTask() {
        var forkJoinPool = new ForkJoinPool();

        var calculator = new FactorialSquareCalculator(10);

        forkJoinPool.execute(calculator);
    }

}

/**
 * A very simple class that calculates the square of an integer. To fit the long running
 * methods category, we're going to put a Thread.sleep call to it so that it last 1 second
 * before completing.
 */
class SquareCalculator {

    private ExecutorService executor
        = Executors.newSingleThreadExecutor(); // handle a single thread at a time.

    public Future<Integer> calculate(Integer input) {
        // Creating an instance of Callable doesn't take us anywhere, we still have to pass
        // this instance to an executor that will take care of starting the task in a new
        // thread, and give us back the valuable Future object.
        return executor.submit(() -> {
            // Callable is an interface representing a task that returns a result, and has a single
            // call() method.
            Thread.sleep(1000);
            return input * input;
        });
        // Return a FutureTask object, which is an implementation of the Future interface.
    }

}

/**
 * A calculator that given an integer will calucate the sum squares for all of its factorial
 * elements.
 */
class FactorialSquareCalculator extends RecursiveTask<Integer> {

    private Integer n;

    public FactorialSquareCalculator(Integer n) {
        this.n = n;
    }

    @Override
    protected Integer compute() {
        // This is where we'll write our business logic.
        if (n <= 1) {
            return n;
        }

        FactorialSquareCalculator calculator
            = new FactorialSquareCalculator(n - 1);
        
        // By calling fork(), a non-blocking method, we ask ForkJoinPool to initiate the execution
        // of this subtask.
        calculator.fork();

        // The join() method will return the result from that calculation, to which we'll add the
        // square of the number we're currently visiting.
        return n * n + calculator.join();
    }

}
